package org.visionlibrary.image.geomtric.util;

import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import org.visionlibrary.image.geomtric.model.Point2d;


public class Point2dHashSet<V extends Point2d> implements Point2dSet<V> {

	private final Map<Bin, Set<V>> points = new HashMap<Bin, Set<V>>();

	private final int neighbourSize;

	private class Bin {
		private final int x;
		private final int y;

		private Bin(V p) {
			this.x = (int) p.x / neighbourSize;
			this.y = (int) p.y / neighbourSize;
		}

		private Bin(double x, double y) {
			this.x = (int) x / neighbourSize;
			this.y = (int) y / neighbourSize;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + getOuterType().hashCode();
			result = prime * result + x;
			result = prime * result + y;
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			Bin other = (Bin) obj;
			if (!getOuterType().equals(other.getOuterType()))
				return false;
			if (x != other.x)
				return false;
			if (y != other.y)
				return false;
			return true;
		}

		private Point2dHashSet<V> getOuterType() {
			return Point2dHashSet.this;
		}
	}

	public Point2dHashSet(int neighbourSize) {
		this.neighbourSize = neighbourSize;
	}

	@Override
	public SortedSet<V> getNearness(final V value) {
		SortedSet<V> result = new TreeSet<V>(new Comparator<V>() {
			@Override
			public int compare(V o1, V o2) {
				return Double.compare(Point2d.distance(o1, value), Point2d
						.distance(o2, value));
			}
		});
		for (int i = -neighbourSize; i <= neighbourSize; i += neighbourSize) {
			for (int j = -neighbourSize; j <= neighbourSize; j += neighbourSize) {
				Bin bin = new Bin(value.x + i, value.y + j);
				result.addAll(findNearest(bin, value));

			}
		}
		return result;
	}

	private Set<V> findNearest(Bin bin, V value) {
		Set<V> set = points.get(bin);
		if (set == null) {
			return Collections.emptySet();
		} else {
			Set<V> result = new HashSet<V>();
			for (V v : set) {
				double d = v.distance(value);
				if (d <= neighbourSize && (d > 0 || !value.equals(v))) {
					result.add(v);
				}

			}
			return result;
		}
	}

	@Override
	public boolean add(V e) {
		Bin bin = new Bin(e);
		if (!points.containsKey(bin)) {
			points.put(bin, new HashSet<V>());
		}
		return points.get(bin).add(e);
	}

	@Override
	public boolean remove(Object o) {
		if (!(o instanceof Point2d)) {
			throw new IllegalStateException("Object nie jest instancją Point2d");
		}
		@SuppressWarnings("unchecked")
		Bin bin = new Bin((V) o);
		if (points.containsKey(bin)) {
			return points.get(bin).remove(o);
		} else {
			return false;
		}
	}

	@Override
	public boolean addAll(Collection<? extends V> collecion) {
		boolean result = true;
		for (V value : collecion) {
			result &= add(value);
		}
		return result;
	}

	@Override
	public void clear() {
		points.clear();
	}

	@Override
	public boolean contains(Object o) {
		return false;
	}

	@Override
	public boolean containsAll(Collection<?> c) {
		return getAllValues().containsAll(c);
	}

	@Override
	public boolean isEmpty() {
		return size() == 0;
	}

	@Override
	public Iterator<V> iterator() {
		@SuppressWarnings("unchecked")
		final Set[] array = points.values().toArray(new Set[] {});
		return new Iterator<V>() {
			private int index = 0;
			@SuppressWarnings("unchecked")
			private Iterator<V> iter = array[0].iterator();

			@Override
			public boolean hasNext() {
				if (iter.hasNext()) {
					return true;
				}
				if (!iter.hasNext() && index + 1 == array.length) {
					return false;
				}
				for (int i = index + 1; i < array.length; i++) {
					if (array[i].size() > 0) {
						return true;
					}
				}
				return false;
			}

			@Override
			@SuppressWarnings("unchecked")
			public V next() {
				if (!iter.hasNext() && index + 1 < array.length) {
					do {
						index++;
						iter = array[index].iterator();
					} while (iter.hasNext());
				}
				return iter.next();
			}

			@Override
			public void remove() {
				iter.remove();
			}
		};
	}

	@Override
	public boolean removeAll(Collection<?> collecion) {
		boolean result = true;
		for (Object value : collecion) {
			result &= remove(value);
		}
		return result;
	}

	@Override
	public boolean retainAll(Collection<?> c) {
		boolean result = true;
		for (Set<V> set : points.values()) {
			result &= set.retainAll(c);
		}
		return result;
	}

	@Override
	public int size() {
		int size = 0;
		for (Set<V> set : points.values()) {
			size += set.size();
		}
		return 0;
	}

	@Override
	public Object[] toArray() {
		return getAllValues().toArray();
	}

	@Override
	public <T> T[] toArray(T[] a) {
		return getAllValues().toArray(a);
	}

	private Set<V> getAllValues() {
		Set<V> all = new HashSet<V>();
		for (Set<V> set : points.values()) {
			all.addAll(set);
		}
		return all;
	}

	@Override
	public int getNeighbourSize() {
		return neighbourSize;
	}

}
